using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Provider;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web.Configuration;
using System.Web.Hosting;
using System.Web.Security;
using MvcCms.Data;
using MvcCms.Service.Cache;
using Microsoft.Practices.ServiceLocation;
using MvcCms.Service.Code.Membership;
using System.Text.RegularExpressions;

namespace MvcCms.Service.Code.Membership
{
    public class MvcCmsMembershipProvider : MembershipProvider
    {
        #region Constructor
        private readonly NameValueCollection _config;
        private readonly ICacheService _cacheService;
        private readonly IAccountService _accountService;
        private readonly string _name = "MvcCmsMembershipProvider";
        public MvcCmsMembershipProvider(IAccountService accountService, ICacheService cacheService)
        {
            _config = cacheService.GetMembershipConfig();
            _accountService = accountService;
            _cacheService = cacheService;
        }
        #endregion

        #region members

        private const int NewPasswordLength = 8;
        private string _applicationName;

        private bool _enablePasswordReset;
        private bool _enablePasswordRetrieval;
        private MachineKeySection _machineKey; // Used when determining encryption key values.
        private int _maxInvalidPasswordAttempts;
        private int _minRequiredNonAlphanumericCharacters;
        private int _minRequiredPasswordLength;
        private int _passwordAttemptWindow;
        private MembershipPasswordFormat _passwordFormat;
        private string _passwordStrengthRegularExpression;
        private bool _requiresQuestionAndAnswer;
        private bool _requiresUniqueEmail;

        #endregion

        #region properties

        public string ValidationKey
        {
            get { return _config["validationKey"]; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return bool.Parse(_config["enablePasswordRetrieval"]); }
        }

        public override bool EnablePasswordReset
        {
            get { return bool.Parse(_config["enablePasswordReset"]); }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return bool.Parse(_config["requiresQuestionAndAnswer"]); }
        }

        public override string ApplicationName { get; set; }

        public override int MaxInvalidPasswordAttempts
        {
            get { return int.Parse(_config["maxInvalidPasswordAttempts"]); }
        }

        public override int PasswordAttemptWindow
        {
            get { return int.Parse(_config["passwordAttemptWindow"]); }
        }

        public override bool RequiresUniqueEmail
        {
            get { return bool.Parse(_config["requiresUniqueEmail"]); }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get
            {
                string tempFormat = _config["passwordFormat"] ?? "Hashed";

                switch (tempFormat)
                {
                    case "Hashed":
                        return MembershipPasswordFormat.Hashed;
                    case "Encrypted":
                        return MembershipPasswordFormat.Encrypted;
                    case "Clear":
                        return MembershipPasswordFormat.Clear;
                }
                return MembershipPasswordFormat.Hashed;
            }
        }

        public override int MinRequiredPasswordLength
        {
            get { return int.Parse(_config["minRequiredPasswordLength"]); }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return int.Parse(_config["minRequiredNonAlphanumericCharacters"]); }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { return _config["passwordStrengthRegularExpression"]; }
        }

        #endregion

        #region public methods

        /// <summary>
        /// Initialize this membership provider. Loads the configuration settings.
        /// </summary>
        /// <param name="name">membership provider name</param>
        /// <param name="config">configuration</param>


        /// <summary>
        /// Adds a new membership user to the data source.
        /// </summary>
        /// <returns>A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the information for the newly created user.</returns>
        /// <param name="username">The user name for the new user.</param>
        /// <param name="password">The password for the new user.</param>
        /// <param name="email">The e-mail address for the new user.</param>
        /// <param name="passwordQuestion">The password question for the new user.</param>
        /// <param name="passwordAnswer">The password answer for the new user</param>
        /// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
        /// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
        /// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus" /> enumeration value indicating whether the user was created successfully.</param>
        public override MembershipUser CreateUser(string username, string password, string email,
                                                  string passwordQuestion, string passwordAnswer, bool isApproved,
                                                  object providerUserKey, out MembershipCreateStatus status)
        {
            // Validate username/password
            var args = new ValidatePasswordEventArgs(username, password, true);
            OnValidatingPassword(args);

            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            // Check whether user with passed username already exists
            MembershipUser u = GetUser(username, false);

            if (u == null)
            {
                DateTime createDate = DateTime.Now;

                if (providerUserKey == null) providerUserKey = Guid.NewGuid();
                else
                {
                    if (!(providerUserKey is Guid))
                    {
                        status = MembershipCreateStatus.InvalidProviderUserKey;
                        return null;
                    }
                }
                var user = new User
                {
                    UserID = (Guid)providerUserKey,
                    UserName = username,
                    ApplicationName = _applicationName,
                    Email = email,
                    Password = EncodePassword(password),
                    PasswordQuestion = passwordQuestion,
                    PasswordAnswer = passwordAnswer,
                    IsApproved = isApproved,
                    LastActivityDate = createDate,
                    LastLoginDate = DateTime.Now,
                    LastPasswordChangedDate = createDate,
                    CreationDate = createDate,
                    IsOnline = false,
                    IsLockedOut = false,
                    LastLockedOutDate = createDate,
                    FailedPasswordAttemptCount = 0,
                    FailedPasswordAttemptWindowStart = createDate,
                    FailedPasswordAnswerAttemptCount = 0,
                    FailedPasswordAnswerAttemptWindowStart = createDate
                };

                try
                {
                    status = _accountService.CreateUser(user)
                                 ? MembershipCreateStatus.Success
                                 : MembershipCreateStatus.UserRejected;
                }
                catch
                {
                    status = MembershipCreateStatus.UserRejected;
                }

                return GetUser(username, false);
            }
            status = MembershipCreateStatus.DuplicateUserName;

            return null;
        }

        /// <summary>
        /// Processes a request to update the password question and answer for a membership user.
        /// </summary>
        /// <returns>true if the password question and answer are updated successfully; otherwise, false.</returns>
        /// <param name="username">The user to change the password question and answer for.</param>
        /// <param name="password">The password for the specified user.</param>
        /// <param name="newPasswordQuestion">The new password question for the specified user.</param>
        /// <param name="newPasswordAnswer">The new password answer for the specified user.</param>
        public override bool ChangePasswordQuestionAndAnswer(string username, string password,
                                                             string newPasswordQuestion, string newPasswordAnswer)
        {
            //check if user is authenticated
            if (!ValidateUser(username, password)) return false;
            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null)
                throw new ProviderException("Change password question and answer failed. No unique user found.");

            user.PasswordAnswer = EncodePassword(newPasswordAnswer);
            user.PasswordQuestion = newPasswordQuestion;

            try
            {
                return _accountService.EditUser(user);
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Gets the password for the specified user name from the data source.
        /// </summary>
        /// <returns>The password for the specified user name.</returns>
        /// <param name="username">The user to retrieve the password for.</param>
        /// <param name="answer">The password answer for the user.</param>
        public override string GetPassword(string username, string answer)
        {
            if (!EnablePasswordRetrieval) throw new ProviderException("Password Retrieval Not Enabled.");
            if (PasswordFormat == MembershipPasswordFormat.Hashed)
                throw new ProviderException("Cannot retrieve Hashed passwords.");

            string password = string.Empty;

            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null) throw new ProviderException("Get password failed. No unique user found.");

            // ReSharper disable ConditionIsAlwaysTrueOrFalse
            if (user != null)
            // ReSharper restore ConditionIsAlwaysTrueOrFalse
            {
                if (Convert.ToBoolean(user.IsLockedOut))
                    throw new MembershipPasswordException("The supplied user is locked out.");
            }
            else throw new MembershipPasswordException("The supplied user name is not found.");

            if (RequiresQuestionAndAnswer && !CheckPassword(answer, user.PasswordAnswer))
            {
                UpdateFailureCount(username, "passwordAnswer");
                throw new MembershipPasswordException("Incorrect password answer.");
            }

            if (PasswordFormat == MembershipPasswordFormat.Encrypted) password = UnEncodePassword(user.Password);

            return password;
        }

        /// <summary>
        /// Processes a request to update the password for a membership user.
        /// </summary>
        /// <returns>true if the password was updated successfully; otherwise, false.</returns>
        /// <param name="username">The user to update the password for.</param>
        /// <param name="oldPassword">The current password for the specified user.</param>
        /// <param name="newPassword">The new password for the specified user.</param>
        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            //check if user is authenticated
            if (!ValidateUser(username, oldPassword)) return false;

            //notify that password is going to change
            var args = new ValidatePasswordEventArgs(username, newPassword, true);
            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null) throw args.FailureInformation;
                throw new MembershipPasswordException("Change password canceled due to new password validation failure.");
            }
            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null) throw new ProviderException("Change password failed. No unique user found.");

            user.Password = EncodePassword(newPassword);
            var theTime = DateTime.Now;
            user.LastPasswordChangedDate = theTime;
            user.LastLoginDate = theTime;
            try
            {
                return _accountService.EditUser(user);
            }
            catch
            {
                return false;
            }
        }
        public bool ChangePasswordIgnoreIsApproved(string username, string oldPassword, string newPassword)
        {
            //check if user is authenticated
            if (!ValidateUserIgnoreIsApproved(username, oldPassword)) return false;

            //notify that password is going to change
            var args = new ValidatePasswordEventArgs(username, newPassword, true);
            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null) throw args.FailureInformation;
                throw new MembershipPasswordException("Change password canceled due to new password validation failure.");
            }
            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null) throw new ProviderException("Change password failed. No unique user found.");

            user.Password = EncodePassword(newPassword);
            var theTime = DateTime.Now;
            user.LastPasswordChangedDate = theTime;
            user.LastLoginDate = theTime;
            try
            {
                return _accountService.EditUser(user);
            }
            catch
            {
                return false;
            }
        }
        public bool ChangePasswordFromEmail(string username, string newPassword)
        {
            //notify that password is going to change
            var args = new ValidatePasswordEventArgs(username, newPassword, true);
            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null) throw args.FailureInformation;
                throw new MembershipPasswordException("Change password canceled due to new password validation failure.");
            }
            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null) throw new ProviderException("Change password failed. No unique user found.");

            user.Password = EncodePassword(newPassword);
            var theTime = DateTime.Now;
            user.LastPasswordChangedDate = theTime;
            user.LastLoginDate = theTime;
            try
            {
                return _accountService.EditUser(user);
            }
            catch
            {
                return false;
            }
        }
        /// <summary>
        /// Processes a request to update the password for a membership user.
        /// </summary>
        /// <returns>true if the password was updated successfully; otherwise, false.</returns>
        /// <param name="username">The user to update the password for.</param>
        /// <param name="newPassword">The new password for the specified user.</param>
        public bool ChangePasswordAdmin(string username, string newPassword)
        {
            var user = _accountService.GetUser_MPCtxt(username);

            if (user == null) throw new ProviderException("Change password failed. No unique user found.");

            user.Password = EncodePassword(newPassword);
            user.LastPasswordChangedDate = DateTime.Now;

            try
            {
                return _accountService.EditUser(user);
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Resets a user's password to a new, automatically generated password.
        /// </summary>
        /// <returns>The new password for the specified user.</returns>
        /// <param name="username">The user to reset the password for.</param>
        /// <param name="answer">The password answer for the specified user.</param>
        public override string ResetPassword(string username, string answer)
        {
            if (!EnablePasswordReset) throw new NotSupportedException("Password reset is not enabled.");

            if (answer == null && RequiresQuestionAndAnswer)
            {
                UpdateFailureCount(username, "passwordAnswer");
                throw new ProviderException("Password answer required for password reset.");
            }

            string newPassword = PasswordGenerator.GeneratePassword(NewPasswordLength);

            var args = new ValidatePasswordEventArgs(username, newPassword, true);
            OnValidatingPassword(args);

            if (args.Cancel)
            {
                if (args.FailureInformation != null) throw args.FailureInformation;
                throw new MembershipPasswordException("Reset password canceled due to password validation failure.");
            }
            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null) throw new ProviderException("Reset password failed. No unique user found.");

            // ReSharper disable ConditionIsAlwaysTrueOrFalse
            if (user != null)
            // ReSharper restore ConditionIsAlwaysTrueOrFalse
            {
                if (Convert.ToBoolean(user.IsLockedOut))
                    throw new MembershipPasswordException("The supplied user is locked out.");
            }
            else
            {
                throw new MembershipPasswordException("The supplied user name is not found.");
            }

            if (RequiresQuestionAndAnswer && (answer != user.PasswordAnswer))
            {
                UpdateFailureCount(username, "passwordAnswer");
                throw new MembershipPasswordException("Incorrect password answer.");
            }

            try
            {
                user.Password = EncodePassword(newPassword);
                user.LastPasswordChangedDate = DateTime.Now;

                _accountService.EditUser(user);
                return newPassword;
            }
            catch
            {
                throw new MembershipPasswordException("User not found, or user is locked out. Password not Reset.");
            }
        }

        /// <summary>
        /// Updates information about a user in the data source.
        /// </summary>
        /// <param name="membershipUser">A <see cref="T:System.Web.Security.MembershipUser" /> object that represents the user to update and the updated information for the user.</param>
        public override void UpdateUser(MembershipUser membershipUser)
        {
            User user = _accountService.GetUser_MPCtxt(membershipUser.UserName);

            if (user == null) throw new ProviderException("Update user failed. No unique user found.");

            // ReSharper disable ConditionIsAlwaysTrueOrFalse
            if (user == null) return;
            // ReSharper restore ConditionIsAlwaysTrueOrFalse

            user.Email = membershipUser.Email;
            user.Comment = membershipUser.Comment;
            user.IsApproved = membershipUser.IsApproved;
            _accountService.EditUser(user);
        }

        /// <summary>
        /// Verifies that the specified user name and password exist in the data source.
        /// </summary>
        /// <returns>true if the specified username and password are valid; otherwise, false.</returns>
        /// <param name="username">The name of the user to validate.</param>
        /// <param name="password">The password for the specified user.</param>
        public bool ValidateUserIgnoreIsApproved(string username, string password)
        {
            bool isValid = false;
            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null) return false;

            if (CheckPassword(password, user.Password))
            {
                Portal currentPortal = _cacheService.GetCurrentPortal();
                if (user.Portal.PortalID == currentPortal.PortalID)
                {
                    isValid = true;
                }
                else
                {
                    bool isPortalAdmin = Roles.IsUserInRole(user.UserName, "PortalAdmin");
                    if (isPortalAdmin)
                    {
                        isValid = true;
                    }
                }
            }
            else
            {
                UpdateFailureCount(username, "password");
            }

            return isValid;
        }

        // Virtual method called when a password is created. The default implementation in MembershipProvider fires a
        // ValidatingPassword event, so be sure to call the base class's OnValidatingPassword method if you override
        // this method. The ValidatingPassword event allows applications to apply additional tests to passwords by
        // registering event handlers.
        // A custom provider's CreateUser, ChangePassword, and ResetPassword methods (in short, all methods that record
        // new passwords) should call this method.
        protected override void OnValidatingPassword(ValidatePasswordEventArgs e)
        {
            if (!string.IsNullOrEmpty(PasswordStrengthRegularExpression) && !Regex.IsMatch(e.Password, PasswordStrengthRegularExpression))
            {
                e.FailureInformation = new Exception("Password must be at least 7 characters long and  & contain at least one number and one character.");
                e.Cancel = true;
            }
            base.OnValidatingPassword(e);
        }

        /// <summary>
        /// Verifies that the specified user name and password exist in the data source.
        /// </summary>
        /// <returns>true if the specified username and password are valid; otherwise, false.</returns>
        /// <param name="username">The name of the user to validate.</param>
        /// <param name="password">The password for the specified user.</param>
        public override bool ValidateUser(string username, string password)
        {
            bool isValid = false;
            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null) return false;

            if (CheckPassword(password, user.Password))
            {
                if (user.IsApproved)
                {
                    Portal currentPortal = _cacheService.GetCurrentPortal();
                    if (user.Portal.PortalID == currentPortal.PortalID)
                    {
                        isValid = true;
                    }
                    else
                    {
                        bool isPortalAdmin = Roles.IsUserInRole(user.UserName, "PortalAdmin");
                        if (isPortalAdmin)
                        {
                            isValid = true;
                        }
                    }
                }
            }
            else
            {
                UpdateFailureCount(username, "password");
            }

            return isValid;
        }

        /// <summary>
        ///  Clears a lock so that the membership user can be validated.
        /// </summary>
        /// <returns>true if the membership user was successfully unlocked; otherwise, false.</returns>
        /// <param name="userName">The membership user whose lock status you want to clear.</param>
        public override bool UnlockUser(string userName)
        {
            try
            {
                User user = _accountService.GetUser_MPCtxt(userName);

                if (user == null) return false;

                user.IsLockedOut = false;
                user.LastLockedOutDate = DateTime.Now;
                _accountService.EditUser(user);
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Gets user information from the data source based on the unique identifier for the membership user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <returns>A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the specified user's information from the data source.</returns>
        /// <param name="providerUserKey">The unique identifier for the membership user to get information for.</param>
        /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            MembershipUser membershipUser = null;
            User user = _accountService.GetUser_MPCtxt((Guid)providerUserKey);

            if (user != null)
            {
                membershipUser = GetMembershipUserFromPersitentObject(user);

                if (userIsOnline)
                {
                    user.LastActivityDate = DateTime.Now;
                    _accountService.EditUser(user);
                }
            }

            return membershipUser;
        }

        /// <summary>
        /// Gets information from the data source for a user. Provides an option to update the last-activity date/time stamp for the user.
        /// </summary>
        /// <returns>A <see cref="T:System.Web.Security.MembershipUser" /> object populated with the specified user's information from the data source.</returns>
        /// <param name="username">The name of the user to get information for.</param>
        /// <param name="userIsOnline">true to update the last-activity date/time stamp for the user; false to return user information without updating the last-activity date/time stamp for the user.</param>
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            MembershipUser membershipUser = null;
            User user = _accountService.GetUser_MPCtxt(username);
            if (user != null)
            {
                membershipUser = GetMembershipUserFromPersitentObject(user);

                if (userIsOnline)
                {
                    user.LastActivityDate = DateTime.Now;
                    _accountService.EditUser(user);
                }
            }
            return membershipUser;
        }

        /// <summary>
        /// Gets the user name associated with the specified e-mail address.
        /// </summary>
        /// <returns>The user name associated with the specified e-mail address. If no match is found, return null.</returns>
        /// <param name="email">The e-mail address to search for.</param>
        public override string GetUserNameByEmail(string email)
        {
            try
            {
                User user = _accountService.GetUserByEmail(email);
                return user != null ? user.UserName : string.Empty;
            }
            catch
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// Removes a user from the membership data source.
        /// </summary>
        /// <returns>true if the user was successfully deleted; otherwise, false.</returns>
        /// <param name="username">The name of the user to delete.</param>
        /// <param name="deleteAllRelatedData">true to delete data related to the user from the database; false to leave data related to the user in the database.</param>
        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            try
            {
                string[] roles = Roles.GetAllRoles();
                foreach (string role in roles)
                {
                    if (Roles.IsUserInRole(username, role))
                    {
                        Roles.RemoveUserFromRole(username, role);
                    }
                }

                bool deleted = _accountService.DeleteUser(username);

                if (deleteAllRelatedData)
                {
                    // TODO: delete user related data
                }
                if (deleted)
                {
                    return true;
                }
                return false;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Gets a collection of all the users in the data source in pages of data.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Web.Security.MembershipUserCollection" /> collection that contains a page of <paramref name="pageSize" /><see cref="T:System.Web.Security.MembershipUser" /> objects beginning at the page specified by <paramref name="pageIndex" />.
        /// </returns>
        /// <param name="pageIndex">The index of the page of results to return. <paramref name="pageIndex" /> is zero-based.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">The total number of matched users.</param>
        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            var users = new MembershipUserCollection();

            //retrieve all users for the current application name from the database
            IEnumerable<User> usercnt = _accountService.ListUsers();

            totalRecords = usercnt.Count();

            if (totalRecords <= 0) return users;

            IEnumerable<User> efUsers = _accountService.ListUsers(pageIndex, pageSize);

            foreach (User user in efUsers)
            {
                users.Add(GetMembershipUserFromPersitentObject(user));
            }

            return users;
        }

        /// <summary>
        /// Gets the number of users currently accessing the application.
        /// </summary>
        /// <returns>The number of users currently accessing the application.</returns>
        public override int GetNumberOfUsersOnline()
        {
            return _accountService.GetNumberOfUsersOnline(System.Web.Security.Membership.UserIsOnlineTimeWindow);
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize,
                                                                 out int totalRecords)
        {
            throw new NotImplementedException();
        }


        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region private methods

        /// <summary>
        /// A helper function that takes the current persistent user and creates a MembershiUser from the values.
        /// </summary>
        /// <param name="user">user object containing the user data retrieved from database</param>
        /// <returns>membership user object</returns>
        private MembershipUser GetMembershipUserFromPersitentObject(User user)
        {
            return new MembershipUser(_name,
                                      user.UserName,
                                      user.UserID,
                                      user.Email,
                                      user.PasswordQuestion,
                                      user.Comment,
                                      user.IsApproved,
                                      Convert.ToBoolean(user.IsLockedOut),
                                      Convert.ToDateTime(user.CreationDate),
                                      Convert.ToDateTime(user.LastLoginDate),
                                      Convert.ToDateTime(user.LastActivityDate),
                                      Convert.ToDateTime(user.LastPasswordChangedDate),
                                      Convert.ToDateTime(user.LastLockedOutDate));
        }

        /// <summary>
        /// A helper method that performs the checks and updates associated with password failure tracking.
        /// </summary>
        private void UpdateFailureCount(string username, string failureType)
        {
            User user = _accountService.GetUser_MPCtxt(username);

            if (user == null) throw new ProviderException("Update failure count failed. No unique user found.");

            var windowStart = new DateTime();
            int failureCount = 0;

            if (failureType == "password")
            {
                failureCount = Convert.ToInt32(user.FailedPasswordAttemptCount);
                windowStart = Convert.ToDateTime(user.FailedPasswordAttemptWindowStart);
            }

            if (failureType == "passwordAnswer")
            {
                failureCount = Convert.ToInt32(user.FailedPasswordAnswerAttemptCount);
                windowStart = Convert.ToDateTime(user.FailedPasswordAnswerAttemptWindowStart);
            }

            DateTime windowEnd = windowStart.AddMinutes(PasswordAttemptWindow);

            if (failureCount == 0 || DateTime.Now > windowEnd)
            {
                // First password failure or outside of PasswordAttemptWindow.
                // Start a new password failure count from 1 and a new window starting now.
                if (failureType == "password")
                {
                    user.FailedPasswordAttemptCount = 1;
                    user.FailedPasswordAttemptWindowStart = DateTime.Now;
                }
                if (failureType == "passwordAnswer")
                {
                    user.FailedPasswordAnswerAttemptCount = 1;
                    user.FailedPasswordAnswerAttemptWindowStart = DateTime.Now;
                }

                try
                {
                    if (!_accountService.EditUser(user))
                    {
                        throw new ProviderException("Unable to update failure count and window start.");
                    }
                }
                catch
                {
                    throw new ProviderException("Unable to update failure count and window start.");
                }
            }
            else
            {
                if (failureCount++ >= MaxInvalidPasswordAttempts)
                {
                    // Max password attempts have exceeded the failure threshold. Lock out the user.
                    user.IsLockedOut = true;
                    user.LastLockedOutDate = DateTime.Now;

                    try
                    {
                        if (!_accountService.EditUser(user))
                        {
                            throw new ProviderException("Unable to lock out user.");
                        }
                    }
                    catch
                    {
                        throw new ProviderException("Unable to lock out user.");
                    }
                }
                else
                {
                    // Max password attempts have not exceeded the failure threshold. Update
                    // the failure counts. Leave the window the same.
                    if (failureType == "password")
                    {
                        user.FailedPasswordAttemptCount = failureCount;
                    }
                    if (failureType == "passwordAnswer")
                    {
                        user.FailedPasswordAnswerAttemptCount = failureCount;
                    }

                    try
                    {
                        if (!_accountService.EditUser(user))
                        {
                            throw new ProviderException("Unable to update failure count.");
                        }
                    }
                    catch
                    {
                        throw new ProviderException("Unable to update failure count.");
                    }
                }
            }
        }

        /// <summary>
        /// Compares password values based on the MembershipPasswordFormat.
        /// </summary>
        /// <param name="password">password</param>
        /// <param name="dbpassword">database password</param>
        /// <returns>whether the passwords are identical</returns>
        private bool CheckPassword(string password, string dbpassword)
        {
            string pass1 = password;
            string pass2 = dbpassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Encrypted:
                    pass2 = UnEncodePassword(dbpassword);
                    break;
                case MembershipPasswordFormat.Hashed:
                    pass1 = EncodePassword(password);
                    break;
                default:
                    break;
            }

            return pass1 == pass2;
        }
        /// <summary>
        /// Encrypts, Hashes, or leaves the password clear based on the PasswordFormat.
        /// </summary>
        /// <param name="password"></param>
        /// <returns></returns>
        private string EncodePassword(string password)
        {
            string encodedPassword = password;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    encodedPassword = Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    var hash = new HMACSHA1 { Key = HexToByte(ValidationKey) };
                    encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
                    break;
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return encodedPassword;
        }
        /// <summary>
        /// Decrypts or leaves the password clear based on the PasswordFormat.
        /// </summary>
        /// <param name="encodedPassword"></param>
        /// <returns></returns>
        private string UnEncodePassword(string encodedPassword)
        {
            string password = encodedPassword;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    password = Encoding.Unicode.GetString(DecryptPassword(Convert.FromBase64String(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    throw new ProviderException("Cannot unencode a hashed password.");
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return password;
        }

        /// <summary>
        /// Converts a hexadecimal string to a byte array. Used to convert encryption key values from the configuration.
        /// </summary>
        /// <param name="hexString"></param>
        /// <returns></returns>
        private static byte[] HexToByte(string hexString)
        {
            var returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
            {
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            }
            return returnBytes;
        }


        #endregion
    }
}